package com.trgzs.bate.common.config;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.trgzs.bate.common.MyAuthenticationSuccessHandler;
import com.trgzs.bate.common.constant.OAuth2Constant;
import com.trgzs.bate.common.security.convert.PasswordAuthenticationConverter;
import com.trgzs.bate.common.security.provider.PasswordAuthenticationProvider;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.service.spi.ServiceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.token.*;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.util.UUID;

/**
 * <p>认证中心配置类</p>
 *
 * @author By: chengxuyuanshitang
 * Ceate Time 2024-05-07 11:42
 */

@Slf4j
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true)
@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig {

    private static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/consent";


    /**
     * Security过滤器链,用于协议端点
     *
     * @param http HttpSecurity
     * @return SecurityFilterChain
     */
    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain (HttpSecurity http,
                                                                       OAuth2AuthorizationService authorizationService,
                                                                       OAuth2TokenGenerator<?> tokenGenerator) throws Exception {
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
                OAuth2AuthorizationServerConfigurer.authorizationServer();


        // 获取授权服务器相关的请求端点
        RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();

        http
                .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
                .with(authorizationServerConfigurer, (authorizationServer) ->
                        authorizationServer
//                                // 客户端身份验证配置器
//                                .clientAuthentication(clientAuthentication ->
//                                        clientAuthentication
//                                                .authenticationConverter(authenticationConverter)
//                                                .authenticationConverters(authenticationConvertersConsumer)
//                                                .authenticationProvider(authenticationProvider)
//                                                .authenticationProviders(authenticationProvidersConsumer)
//                                                .authenticationSuccessHandler(authenticationSuccessHandler)
//                                                .errorResponseHandler(errorResponseHandler)
//                                )
                                // 授权端点
                                .authorizationEndpoint(authorizationEndpoint ->
                                        authorizationEndpoint
//                                                .authorizationRequestConverter(new PasswordAuthenticationConverter())
//                                                .authorizationRequestConverters(authorizationRequestConvertersConsumer)
//                                                .authenticationProvider(new PasswordAuthenticationProvider(authorizationService, tokenGenerator))
//                                                .authenticationProviders(authenticationProvidersConsumer)
//                                                .authorizationResponseHandler(authorizationResponseHandler)
//                                                .errorResponseHandler(errorResponseHandler)
                                                .consentPage(CUSTOM_CONSENT_PAGE_URI)
                                )
//                                // 设备授权端点
//                                .deviceAuthorizationEndpoint()
//                                // 设备验证端点
//                                .deviceVerificationEndpoint()
                                // 自定义OAuth2端点
                                .tokenEndpoint(tokenEndpoint ->
                                        tokenEndpoint
                                                .accessTokenRequestConverter(new PasswordAuthenticationConverter())
                                                .authenticationProvider(new PasswordAuthenticationProvider(authorizationService, tokenGenerator))
                                                .accessTokenResponseHandler(new MyAuthenticationSuccessHandler())
                                )
//                                // 自定义OAuth2自省端点
//                                .tokenIntrospectionEndpoint()
//                                // 自定义撤销端点
//                                .tokenRevocationEndpoint()
//                                // 自定义 OAuth2 授权服务器元数据端点
//                                .authorizationServerMetadataEndpoint()
                                // 自定义
//                                .tokenGenerator()
                                .oidc(Customizer.withDefaults())	// Enable OpenID Connect 1.0
                )
                // Redirect to the login page when not authenticated from the
                // authorization endpoint
                .exceptionHandling((exceptions) -> exceptions
                        .defaultAuthenticationEntryPointFor(
                                new LoginUrlAuthenticationEntryPoint("/login"),
                                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                        )
                );

        return http.build ();
    }

    /**
     * 用于认证的Spring Security过滤器链。
     *
     * @param http HttpSecurity
     * @return SecurityFilterChain
     */
    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain (HttpSecurity http) throws Exception {
        http.authorizeHttpRequests ((authorize) -> authorize
                        .requestMatchers (new AntPathRequestMatcher ("/actuator/**"),
                                new AntPathRequestMatcher ("/druid/*"),
                                new AntPathRequestMatcher ("/oauth2/**"),
                                new AntPathRequestMatcher ("/**/*.json"),
                                new AntPathRequestMatcher ("/**/*.css"),
                                new AntPathRequestMatcher ("/**/*.html")).permitAll ()
                        .anyRequest ().authenticated ()
                )
                .exceptionHandling(e -> e
                        .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
                )
                .oauth2ResourceServer(resourceServer ->
                        resourceServer
                                .jwt(Customizer.withDefaults())
                )
//                .oauth2Client(Customizer.withDefaults())
//                .oauth2Login(oauth2Login ->
//                        oauth2Login.loginPage("/oauth2/authorization/demo-client-name"));
                .formLogin(Customizer.withDefaults());
        return http.build ();
    }

    /**
     * 配置密码解析器，使用BCrypt的方式对密码进行加密和验证
     *
     * @return BCryptPasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder () {
        return new BCryptPasswordEncoder ();
    }

    @Bean
    public UserDetailsService userDetailsService () {
        UserDetails userDetails = User.withUsername ("admin")
                .password (passwordEncoder ().encode ("admin"))
                .roles ("admin")
                .authorities("admin")
                .build ();
        return new InMemoryUserDetailsManager (userDetails);
    }

    /**
     * RegisteredClientRepository 的一个实例，用于管理客户端
     *
     * @param jdbcTemplate    jdbcTemplate
     * @param passwordEncoder passwordEncoder
     * @return RegisteredClientRepository
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository (JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {
        RegisteredClient registeredClient = RegisteredClient.withId (UUID.randomUUID ().toString ())
                .clientId ("oauth2-client")
                .clientSecret (passwordEncoder.encode ("123456"))
                // 客户端认证基于请求头
                .clientAuthenticationMethod (ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                // 配置授权的支持方式
                .authorizationGrantType (AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType (AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType (AuthorizationGrantType.CLIENT_CREDENTIALS)
                .redirectUri ("https://www.baidu.com")
                .scope ("user")
                .scope ("admin")
                // 客户端设置，设置用户需要确认授权
                .clientSettings (ClientSettings.builder ().requireAuthorizationConsent (true).build ())
                .tokenSettings(TokenSettings.builder()
                        .accessTokenTimeToLive(Duration.ofDays(1))
                        .refreshTokenTimeToLive(Duration.ofDays(30))
                        .build())
                .build ();

        JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository (jdbcTemplate);
        RegisteredClient repositoryByClientId = registeredClientRepository.findByClientId (registeredClient.getClientId ());
        if (repositoryByClientId == null) {
            registeredClientRepository.save (registeredClient);
        }

        // 修改之后需要数据库同步
        RegisteredClient passwordRegisteredClient = RegisteredClient.withId (UUID.randomUUID ().toString ())
                .clientId ("oauth2-client-password")
                .clientSecret (passwordEncoder.encode ("123456"))
                // 客户端认证基于请求头
                .clientAuthenticationMethod (ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                // 配置授权的支持方式
                .authorizationGrantType (new AuthorizationGrantType(OAuth2Constant.GRANT_TYPE_PASSWORD))
                .authorizationGrantType (AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType (AuthorizationGrantType.CLIENT_CREDENTIALS)
                .scope ("user")
                .scope ("admin")
                // 客户端设置，设置用户需要确认授权
                .clientSettings (ClientSettings.builder ().requireAuthorizationConsent (true).build ())
                .tokenSettings(TokenSettings.builder()
                        .accessTokenTimeToLive(Duration.ofDays(1))
                        .refreshTokenTimeToLive(Duration.ofDays(30))
                        .build())
                .build ();

        RegisteredClient passwordRepositoryByClientId = registeredClientRepository.findByClientId (passwordRegisteredClient.getClientId ());
        if (passwordRepositoryByClientId == null) {
            registeredClientRepository.save (passwordRegisteredClient);
        }

        return registeredClientRepository;
    }

    /**
     * 授权信息
     * 对应表：oauth2_authorization
     */
    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
    }

    /**
     * 用于签署访问令牌
     *
     * @return JWKSource
     */
    @Bean
    public JWKSource<SecurityContext> jwkSource () {
        KeyPair keyPair = generateRsaKey ();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic ();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate ();
        RSAKey rsaKey = new RSAKey.Builder (publicKey)
                .privateKey (privateKey)
                .keyID (UUID.randomUUID ().toString ())
                .build ();
        JWKSet jwkSet = new JWKSet (rsaKey);
        return new ImmutableJWKSet<> (jwkSet);
    }

    /**
     * 创建RsaKey
     *
     * @return KeyPair
     */
    private static KeyPair generateRsaKey () {
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance ("RSA");
            keyPairGenerator.initialize (2048);
            keyPair = keyPairGenerator.generateKeyPair ();
        } catch (Exception e) {
            log.error ("generateRsaKey Exception", e);
            throw new ServiceException("generateRsaKey Exception");
        }
        return keyPair;
    }

    /**
     * 解码签名访问令牌
     *
     * @param jwkSource jwkSource
     * @return JwtDecoder
     */
    @Bean
    public JwtDecoder jwtDecoder (JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder (jwkSource);
    }

    /**
     * 授权服务器配置设置。指定协议端点URI
     * @return
     */
    @Bean
    public AuthorizationServerSettings authorizationServerSettings () {
        return AuthorizationServerSettings.builder ().build ();
    }

    /**
     *配置token生成器
     */
    @Bean
    OAuth2TokenGenerator<?> tokenGenerator(JWKSource<SecurityContext> jwkSource) {
        JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource));
        OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
        OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
        return new DelegatingOAuth2TokenGenerator(
                jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
    }

}
